# Python 趣味编程：第 11 章
# 练习问题 11.1 自动打开四周
# --------------------------
# 程序名: ex11-auto-open.py

from tkinter import Tk, Canvas
from dataclasses import dataclass, field
import math
import random

from p10cell import Cell

# =================================================
# 初期設定値(固定値)
OFFSET_X = 50
OFFSET_Y = 50
CELL_SIZE = 30
FONT_SIZE = 20

BOARD_WIDTH = 10
BOARD_HEIGHT = 8
NUM_MINES = 2

BOARD_RIGHT = 2 * OFFSET_X + CELL_SIZE * BOARD_WIDTH
BOARD_BOTTOM = 2 * OFFSET_Y + CELL_SIZE * BOARD_HEIGHT

FONT = "Helvetica " + str(FONT_SIZE)

# -------------------
# 管理整个游戏的棋局
@dataclass
class Board:
    canvas: Canvas
    width: int
    height: int
    cell_size: int
    offset_x: int
    offset_y: int
    font: str
    mine: set = field(init=False, default_factory=set)
    cell: Cell = field(init = False)

    def __post_init__(self):
        self.cell = Cell(self.canvas, self.width, self.height,
                         self.cell_size, self.offset_x, self.offset_y,
                         self.font)
        for i in range(self.width):
            for j in range(self.height):
                self.cell.draw(i, j)

    # 做游戏的准备
    def setup(self, num_mines):
        # 注册活动程序
        canvas.bind('<Button-1>', self.on_click_left)
        canvas.bind('<Button-2>', self.on_click_right)
        self.set_mine(num_mines)

    # 设置地雷
    def set_mine(self, num):
        num_mine = 0
        while num_mine < num:  # 按指定数量制造地雷
            i = random.randrange(self.width)   # 横方向
            j = random.randrange(self.height)  # 纵方向
            if not (i, j) in self.mine: # 如果地雷还没有被设置的话
                self.mine.add((i, j))   # 在这里设置地雷
                num_mine += 1  # 将已经生成的地雷数增加1
                print("mine =", i, ",", j)     # 开发时的确认用

    # 方格索引是否有效的判断
    def is_valid(self, i, j):
        return 0 <= i < self.width and 0 <= j < self.height

    # 打开方格
    def open(self, i, j):
        if self.is_valid(i, j):       # 如果方格索引有效的话
            if (i, j) in self.mine:   # 如果是地雷
                text = "*"            # "*"
            else:                     # 如果不是地雷的话
                text = str(self.count(i, j))  # 计数数字表示
            self.cell.open(i, j) # 打开方格
            self.cell.draw(i, j, text)  # 重新绘制

    # 创建表示周围方格的元组列表
    def neighbors(self, i, j):
        x = [(i-1, j-1), (i, j-1), (i+1, j-1),
             (i-1, j  ),           (i+1, j  ),
             (i-1, j+1), (i, j+1), (i+1, j+1)]
        value = [v for v in x if self.is_valid(v[0], v[1])]
        return value

    # 打开自己周围的方格
    def open_neighbors(self, i, j):
        if self.count(i, j)==0:  # 如果(i,j)格的数字是0
            for (xi, xj) in self.neighbors(i, j):
                self.open(xi, xj)

    # 数周围地雷的数量
    def count(self, i, j):
        c = 0
        for x in self.neighbors(i, j):   # 取出元组
            if x in self.mine:           # 如果是地雷
                c = c + 1                # 向上计数 
        return c

    # 根据屏幕的坐标(x,y)求索引(i,j)
    def get_index(self, x, y):
        i = math.floor((x - OFFSET_X) / CELL_SIZE) # 从x计算索引j
        j = math.floor((y - OFFSET_Y) / CELL_SIZE) # 从y计算索引i
        return (i, j)

    # 点击右键时的处理
    def on_click_right(self, event):
        (i, j) = self.get_index(event.x, event.y)
        # print("右", i, j)  # 在调试时确认计算正确的方法的例子
        if self.is_valid(i, j):  # 有效的索引
            if not self.cell.is_open(i, j):  # 如果还没有打开
                self.cell.update(i, j)   # 改变标志的状态
                self.cell.draw(i, j)     # 重新绘制

    # 点击左键时的处理
    def on_click_left(self, event):
        (i, j) = self.get_index(event.x, event.y)
        # print("左", i, j)  # 在调试时确认计算正确的方法的例子
        if self.is_valid(i, j):  # 有效的索引
            if not self.cell.is_open(i, j):  # 如果还没有打开
                self.open(i, j)  # 打开方格
                self.open_neighbors(i, j) # 如果是零，打开周围

# -------------------
tk = Tk()
tk.title("Mine Sweeper")

canvas = Canvas(tk, width=BOARD_RIGHT, height=BOARD_BOTTOM, bd=0)
canvas.pack()
# ----------------------------------
# 主程序
board = Board(canvas, BOARD_WIDTH, BOARD_HEIGHT,
              CELL_SIZE, OFFSET_X, OFFSET_Y, FONT)
board.setup(NUM_MINES)
